// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package modfetch

import (
	"archive/zip"
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/fsys"
	"cmd/go/internal/lockedfile"
	"cmd/go/internal/par"
	"cmd/go/internal/robustio"
	"cmd/go/internal/trace"

	"golang.org/x/mod/module"
	"golang.org/x/mod/sumdb/dirhash"
	modzip "golang.org/x/mod/zip"
)

var downloadCache par.Cache

// 下载将特定模块版本下载到
// 本地下载缓存，并返回模块文件树根目录
// 的名称。标准杆数46×*/。这里的缓存可以避免重复工作。
func Download(ctx context.Context, mod module.Version) (dir string, err error) {
	if err := checkCacheDir(); err != nil {
		base.Fatalf("go: %v", err)
	}

	type cached struct {
		dir string
		err error
	}
	c := downloadCache.Do(mod, func() any {
		dir, err := download(ctx, mod)
		if err != nil {
			return cached{"", err}
		}
		checkMod(mod)
		return cached{dir, nil}
	}).(cached)
	return c.dir, c.err
}

func download(ctx context.Context, mod module.Version) (dir string, err error) {
	ctx, span := trace.StartSpan(ctx, "modfetch.download "+mod.String())
	defer span.Done()

	dir, err = DownloadDir(mod)
	if err == nil {
		// 目录已被完全解压缩（不存在部分文件）。
		return dir, nil
	} else if dir == "" || !errors.Is(err, fs.ErrNotExist) {
		return "", err
	}

	// 为避免缓存中出现多余文件，
	// DownloadZip使用与Download相同的锁文件。
	// 在锁定文件之前调用DownloadZip。
	zipfile, err := DownloadZip(ctx, mod)
	if err != nil {
		return "", err
	}

	unlock, err := lockVersion(mod)
	if err != nil {
		return "", err
	}
	defer unlock()

	ctx, span = trace.StartSpan(ctx, "unzip "+zipfile)
	defer span.Done()

	// 检查在等待锁时目录是否已填充。
	_, dirErr := DownloadDir(mod)
	if dirErr == nil {
		return dir, nil
	}
	_, dirExists := dirErr.(*DownloadDirPartialError)

	// 清除旧版本
	// （1.16之前）创建的所有剩余临时目录，以及部分提取的目录（由
	// DownloadDirPartialError指示，通常是因为.partial文件）。这只是
	// 可以安全执行，因为锁文件确保其写入程序不再处于活动状态。
	parentDir := filepath.Dir(dir)
	tmpPrefix := filepath.Base(dir) + ".tmp-"
	if old, err := filepath.Glob(filepath.Join(parentDir, tmpPrefix+"*")); err == nil {
		for _, path := range old {
			RemoveAll(path) // 尽最大努力
		}
	}
	if dirExists {
		if err := RemoveAll(dir); err != nil {
			return "", err
		}
	}

	partialPath, err := CachePath(mod, "partial")
	if err != nil {
		return "", err
	}

	// 在其最终位置提取模块zip目录。
	// 
	// 为了防止其他进程在崩溃时读取目录，
	// 创建一个。解压目录前的部分文件，并删除
	// 。之后进行部分归档（同时持有锁）。
	// 
	// 在Go 1.16之前，我们将其解压到一个临时目录中，并使用随机名称
	// 然后用os将其重命名。重命名。在Windows上，当另一个进程（通常是反病毒扫描程序）打开临时目录中的文件时，由于
	// 错误_访问_被拒绝，此操作失败。
	// 
	// Go 1.14.2及更高版本。部分文件。旧版本可能会使用
	// 部分提取的目录。”go mod verify'可以检测到这一点，
	// 和go clean-modcache'可以修复它。
	if err := os.MkdirAll(parentDir, 0777); err != nil {
		return "", err
	}
	if err := os.WriteFile(partialPath, nil, 0666); err != nil {
		return "", err
	}
	if err := modzip.Unzip(dir, mod, zipfile); err != nil {
		fmt.Fprintf(os.Stderr, "-> %s\n", err)
		if rmErr := RemoveAll(dir); rmErr == nil {
			os.Remove(partialPath)
		}
		return "", err
	}
	if err := os.Remove(partialPath); err != nil {
		return "", err
	}

	if !cfg.ModCacheRW {
		makeDirsReadOnly(dir)
	}
	return dir, nil
}

var downloadZipCache par.Cache

// DownloadZip将特定模块版本下载到
// 本地zip缓存，并返回zip文件的名称。ABCFDG＊＊＊＊＊＊＊＊＊＊＊＊＊。这里的缓存可以避免重复工作。
func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) {
	type cached struct {
		zipfile string
		err     error
	}
	c := downloadZipCache.Do(mod, func() any {
		zipfile, err := CachePath(mod, "zip")
		if err != nil {
			return cached{"", err}
		}
		ziphashfile := zipfile + "hash"

		// 如果zip和ziphash文件存在，则返回而不锁定。
		if _, err := os.Stat(zipfile); err == nil {
			if _, err := os.Stat(ziphashfile); err == nil {
				return cached{zipfile, nil}
			}
		}

		// zip或ziphash文件不存在。获取锁并创建它们。
		if cfg.CmdName != "mod download" {
			fmt.Fprintf(os.Stderr, "go: downloading %s %s\n", mod.Path, mod.Version)
		}
		unlock, err := lockVersion(mod)
		if err != nil {
			return cached{"", err}
		}
		defer unlock()

		if err := downloadZip(ctx, mod, zipfile); err != nil {
			return cached{"", err}
		}
		return cached{zipfile, nil}
	}).(cached)
	return c.zipfile, c.err
}

func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) {
	ctx, span := trace.StartSpan(ctx, "modfetch.downloadZip "+zipfile)
	defer span.Done()

	// 在等待
	// 下载zip中的锁时，请仔细检查zipfile是否未创建。
	ziphashfile := zipfile + "hash"
	var zipExists, ziphashExists bool
	if _, err := os.Stat(zipfile); err == nil {
		zipExists = true
	}
	if _, err := os.Stat(ziphashfile); err == nil {
		ziphashExists = true
	}
	if zipExists && ziphashExists {
		return nil
	}

	// 创建父目录。
	if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil {
		return err
	}

	// 清除以前运行的所有剩余临时文件。
	// 这样做是安全的，因为锁文件确保他们的
	// 写入程序不再处于活动状态。
	tmpPattern := filepath.Base(zipfile) + "*.tmp"
	if old, err := filepath.Glob(filepath.Join(filepath.Dir(zipfile), tmpPattern)); err == nil {
		for _, path := range old {
			os.Remove(path) // 尽最大努力
		}
	}

	// 如果zip文件存在，ziphash文件必须已被删除
	// 或在文件系统崩溃后丢失。在不下载的情况下重新散列zip。
	if zipExists {
		return hashZip(mod, zipfile, ziphashfile)
	}

	// 从这里到操作系统。下面的重命名调用在功能上几乎等同于
	// Rename IO。WriteToFile，有一个关键区别：我们希望在提交之前验证文件的
	// 内容（通过散列）。因为文件
	// 是压缩的，所以我们需要一个实际的文件——或者至少一个io。ReaderAt-to 
	// 验证它：我们不能在编写流时就使用tee。
	f, err := os.CreateTemp(filepath.Dir(zipfile), tmpPattern)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			f.Close()
			os.Remove(f.Name())
		}
	}()

	var unrecoverableErr error
	err = TryProxies(func(proxy string) error {
		if unrecoverableErr != nil {
			return unrecoverableErr
		}
		repo := Lookup(proxy, mod.Path)
		err := repo.Zip(f, mod.Version)
		if err != nil {
			// Zip在失败之前可能已经部分写入了f。
			// （可能是服务器在发送文件时崩溃了？）
			// 由于我们在某些情况下允许错误回退，我们需要将
			// 文件重新设置为空，以便下次尝试。
			if _, err := f.Seek(0, io.SeekStart); err != nil {
				unrecoverableErr = err
				return err
			}
			if err := f.Truncate(0); err != nil {
				unrecoverableErr = err
				return err
			}
		}
		return err
	})
	if err != nil {
		return err
	}

	// 仔细检查zip文件中的路径是否格式正确。
	// 
	// TODO（bcmills）：解压函数中有一个类似的检查。我们能消除一个吗？
	fi, err := f.Stat()
	if err != nil {
		return err
	}
	z, err := zip.NewReader(f, fi.Size())
	if err != nil {
		return err
	}
	prefix := mod.Path + "@" + mod.Version + "/"
	for _, f := range z.File {
		if !strings.HasPrefix(f.Name, prefix) {
			return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name)
		}
	}

	if err := f.Close(); err != nil {
		return err
	}

	// 对zip文件进行哈希运算，并在重命名到最终位置之前检查总和。
	if err := hashZip(mod, f.Name(), ziphashfile); err != nil {
		return err
	}
	if err := os.Rename(f.Name(), zipfile); err != nil {
		return err
	}

	// TODO（bcmills）：我们应该做出决定吗。拉上拉链。ziphash文件是只读的，以防止篡改？

	return nil
}

// hashZip读取在f中打开的zip文件，然后将哈希写入ziphashfile，
// 覆盖该文件（如果存在）。
// 
// 如果哈希不匹配，则转到。sum（或sumdb，如果启用），hashZip返回
// 一个错误，并且不写入ziphashfile。
func hashZip(mod module.Version, zipfile, ziphashfile string) (err error) {
	hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash)
	if err != nil {
		return err
	}
	if err := checkModSum(mod, hash); err != nil {
		return err
	}
	hf, err := lockedfile.Create(ziphashfile)
	if err != nil {
		return err
	}
	defer func() {
		if closeErr := hf.Close(); err == nil && closeErr != nil {
			err = closeErr
		}
	}()
	if err := hf.Truncate(int64(len(hash))); err != nil {
		return err
	}
	if _, err := hf.WriteAt([]byte(hash), 0); err != nil {
		return err
	}
	return nil
}

// makedirsreadoly尽最大努力删除dir 
// 及其可传递内容的写入权限。
func makeDirsReadOnly(dir string) {
	type pathMode struct {
		path string
		mode fs.FileMode
	}
	var dirs []pathMode // 按词法顺序
	filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
		if err == nil && d.IsDir() {
			info, err := d.Info()
			if err == nil && info.Mode()&0222 != 0 {
				dirs = append(dirs, pathMode{path, info.Mode()})
			}
		}
		return nil
	})

	// 在父母之前将列表向后滚动到chmod儿童。
	for i := len(dirs) - 1; i >= 0; i-- {
		os.Chmod(dirs[i].path, dirs[i].mode&^0222)
	}
}

// RemoveAll删除通过下载或解压写入的目录，首先应用
// 执行此操作所需的任何权限更改。
func RemoveAll(dir string) error {
	// 模块缓存有0555个目录；使其可写以删除内容。
	filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error {
		if err != nil {
			return nil // 忽略文件系统中的错误。总和通过软件包modload 
		}
		if info.IsDir() {
			os.Chmod(path, 0777)
		}
		return nil
	})
	return robustio.RemoveAll(dir)
}

var WorkspaceGoSumFiles []string // 设置模块的运行路径。工作空间中的总和；由软件包modload 

type modSum struct {
	mod module.Version
	sum string
}

var goSum struct {
	mu        sync.Mutex
	m         map[module.Version][]string            // 设置go的内容。sum文件
	w         map[string]map[module.Version][]string // 工作区中的sum文件->该sum文件的内容
	status    map[modSum]modSumStatus                // m 
	enabled   bool                                   // 是否使用go。sum at all 
}

type modSumStatus struct {
	used, dirty bool
}

// Reset重置modfetch包中的全局变量，因此之前的加载不会影响go的内容。sum files 
func Reset() {
	GoSumFile = ""
	WorkspaceGoSumFiles = nil

	// lookupCache和downloadCache的用法都可以调用checkModSum，
	// 从而设置goSum上使用的位。模块的状态。
	// 重置它们，以便正确计算所使用的值。
	lookupCache = par.Cache{}
	downloadCache = par.Cache{}

	// 清除goSum上的所有字段。稍后将初始化
	goSum.mu.Lock()
	goSum.m = nil
	goSum.w = nil
	goSum.status = nil
	goSum.overwrite = false
	goSum.enabled = false
	goSum.mu.Unlock()
}

// initGoSum初始化go。求和数据。
// 返回的布尔值报告
// 是否使用go。sum现在已启用。
// 必须持有goSum锁。
func initGoSum() (bool, error) {
	if GoSumFile == "" {
		return false, nil
	}
	if goSum.m != nil {
		return true, nil
	}

	goSum.m = make(map[module.Version][]string)
	goSum.status = make(map[modSum]modSumStatus)
	goSum.w = make(map[string]map[module.Version][]string)

	for _, f := range WorkspaceGoSumFiles {
		goSum.w[f] = make(map[module.Version][]string)
		_, err := readGoSumFile(goSum.w[f], f)
		if err != nil {
			return false, err
		}
	}

	enabled, err := readGoSumFile(goSum.m, GoSumFile)
	goSum.enabled = enabled
	return enabled, err
}

func readGoSumFile(dst map[module.Version][]string, file string) (bool, error) {
	var (
		data []byte
		err  error
	)
	if actualSumFile, ok := fsys.OverlayPath(file); ok {
		// 不要锁定go。如果是叠加的一部分，求和。
		// 在计划9中，锁定需要chmod，我们不想修改覆盖中的任何文件
		// 。见#44700。
		data, err = os.ReadFile(actualSumFile)
	} else {
		data, err = lockedfile.Read(file)
	}
	if err != nil && !os.IsNotExist(err) {
		return false, err
	}
	readGoSum(dst, file, data)

	return true, nil
}

// emptyGoModHash是包含0长度go的1文件树的哈希。摩登派青年
// 一个错误导致我们将这些写入go。非模块的总和文件。
// 我们检测并删除它们。
const emptyGoModHash = "h1:G7mAYYxgmS0lVkHyy2hEOLQCFB0DlQFTMLWggykrydY="

// readGoSum解析数据，即文件内容，并将其添加到goSum。m、 必须持有goSum锁。
func readGoSum(dst map[module.Version][]string, file string, data []byte) error {
	lineno := 0
	for len(data) > 0 {
		var line []byte
		lineno++
		i := bytes.IndexByte(data, '\n')
		if i < 0 {
			line, data = data, nil
		} else {
			line, data = data[:i], data[i+1:]
		}
		f := strings.Fields(string(line))
		if len(f) == 0 {
			// 空行；跳过它
			continue
		}
		if len(f) != 3 {
			return fmt.Errorf("malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
		}
		if f[2] == emptyGoModHash {
			// 老错误；放下它。
			continue
		}
		mod := module.Version{Path: f[0], Version: f[1]}
		dst[mod] = append(dst[mod], f[2])
	}
	return nil
}

// HaveSum返回true，如果。sum文件包含mod的一个条目。
// 必须使用已知的哈希算法生成条目的哈希。
// mod。版本可能有一个“/go.mod”后缀来区分
// 的和。国防部和。压缩文件。
func HaveSum(mod module.Version) bool {
	goSum.mu.Lock()
	defer goSum.mu.Unlock()
	inited, err := initGoSum()
	if err != nil || !inited {
		return false
	}
	for _, goSums := range goSum.w {
		for _, h := range goSums[mod] {
			if !strings.HasPrefix(h, "h1:") {
				continue
			}
			if !goSum.status[modSum{mod, h}].dirty {
				return true
			}
		}
	}
	for _, h := range goSum.m[mod] {
		if !strings.HasPrefix(h, "h1:") {
			continue
		}
		if !goSum.status[modSum{mod, h}].dirty {
			return true
		}
	}
	return false
}

// checkMod检查给定模块的校验和。
func checkMod(mod module.Version) {
	// 在获取go之前进行文件I/O。总和锁。
	ziphash, err := CachePath(mod, "ziphash")
	if err != nil {
		base.Fatalf("verifying %v", module.VersionError(mod, err))
	}
	data, err := lockedfile.Read(ziphash)
	if err != nil {
		base.Fatalf("verifying %v", module.VersionError(mod, err))
	}
	data = bytes.TrimSpace(data)
	if !isValidSum(data) {
		// 从zip文件重新创建ziphash文件，并使用该文件检查mod sum。
		zip, err := CachePath(mod, "zip")
		if err != nil {
			base.Fatalf("verifying %v", module.VersionError(mod, err))
		}
		err = hashZip(mod, zip, ziphash)
		if err != nil {
			base.Fatalf("verifying %v", module.VersionError(mod, err))
		}
		return
	}
	h := string(data)
	if !strings.HasPrefix(h, "h1:") {
		base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h)))
	}

	if err := checkModSum(mod, h); err != nil {
		base.Fatalf("%s", err)
	}
}

// goModSum返回go的校验和。修改内容。
func goModSum(data []byte) (string, error) {
	return dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
		return io.NopCloser(bytes.NewReader(data)), nil
	})
}

// checkGoMod检查给定模块的运行。模校验和；
// 数据就是通行证。修改内容。
func checkGoMod(path, version string, data []byte) error {
	h, err := goModSum(data)
	if err != nil {
		return &module.ModuleError{Path: path, Version: version, Err: fmt.Errorf("verifying go.mod: %v", err)}
	}

	return checkModSum(module.Version{Path: path, Version: version + "/go.mod"}, h)
}

// checkModSum检查mod的记录校验和是否为h。
// 
// mod。版本可能会有额外的后缀“/go.mod”，以请求模块go的校验和
// 。仅限mod文件。
func checkModSum(mod module.Version, h string) error {
	// 我们在操作goSum时会锁定goSum，
	// 但是我们在调用checkSumDB时会释放锁，
	// 这样对checkModHash的并行调用就可以执行对checkSumDB的并行调用
	// 了。

	// 检查go中是否列出mod+h。已经有了。如果是这样，我们就完了。
	goSum.mu.Lock()
	inited, err := initGoSum()
	if err != nil {
		goSum.mu.Unlock()
		return err
	}
	done := inited && haveModSumLocked(mod, h)
	if inited {
		st := goSum.status[modSum{mod, h}]
		st.used = true
		goSum.status[modSum{mod, h}] = st
	}
	goSum.mu.Unlock()

	if done {
		return nil
	}

	// 未列出，因此我们想添加它们。
	// 如果合适，请咨询校验和数据库。
	if useSumDB(mod) {
		// 呼叫基地。如果检测到不匹配，则返回Fatalf。
		if err := checkSumDB(mod, h); err != nil {
			return err
		}
	}

	// 添加mod+h即可。如果它还没有出现的话。
	if inited {
		goSum.mu.Lock()
		addModSumLocked(mod, h)
		st := goSum.status[modSum{mod, h}]
		st.dirty = true
		goSum.status[modSum{mod, h}] = st
		goSum.mu.Unlock()
	}
	return nil
}

// haveModSumlock报告mod，h对是否已在go中列出。总和
// 如果它找到了一个冲突的对，则调用base。法塔尔夫。
// goSum。一定是锁上了。
func haveModSumLocked(mod module.Version, h string) bool {
	sumFileName := "go.sum"
	if strings.HasSuffix(GoSumFile, "go.work.sum") {
		sumFileName = "go.work.sum"
	}
	for _, vh := range goSum.m[mod] {
		if h == vh {
			return true
		}
		if strings.HasPrefix(vh, "h1:") {
			base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s:     %v"+goSumMismatch, mod.Path, mod.Version, h, sumFileName, vh)
		}
	}
	// 同时检查工作区总数。
	foundMatch := false
	// 检查所有文件的总和，以防
	// 文件之间存在冲突。
	for goSumFile, goSums := range goSum.w {
		for _, vh := range goSums[mod] {
			if h == vh {
				foundMatch = true
			} else if strings.HasPrefix(vh, "h1:") {
				base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s:     %v"+goSumMismatch, mod.Path, mod.Version, h, goSumFile, vh)
			}
		}
	}
	return foundMatch
}

// addmodsumlock添加一对mod，h to go。总和
// goSum。一定是锁上了。
func addModSumLocked(mod module.Version, h string) {
	if haveModSumLocked(mod, h) {
		return
	}
	if len(goSum.m[mod]) > 0 {
		fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(goSum.m[mod], ", "), h)
	}
	goSum.m[mod] = append(goSum.m[mod], h)
}

// checkSumDB根据Go校验和数据库检查mod，h对。
// 它调用base。如果要拒绝哈希，则返回Fatalf。
func checkSumDB(mod module.Version, h string) error {
	modWithoutSuffix := mod
	noun := "module"
	if strings.HasSuffix(mod.Version, "/go.mod") {
		noun = "go.mod"
		modWithoutSuffix.Version = strings.TrimSuffix(mod.Version, "/go.mod")
	}

	db, lines, err := lookupSumDB(mod)
	if err != nil {
		return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: %v", noun, err))
	}

	have := mod.Path + " " + mod.Version + " " + h
	prefix := mod.Path + " " + mod.Version + " h1:"
	for _, line := range lines {
		if line == have {
			return nil
		}
		if strings.HasPrefix(line, prefix) {
			return module.VersionError(modWithoutSuffix, fmt.Errorf("verifying %s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+sumdbMismatch, noun, h, db, line[len(prefix)-len("h1:"):]))
		}
	}
	return nil
}

// Sum返回给定模块的下载副本的校验和，如果下载缓存中存在，则返回
// 的校验和。
func Sum(mod module.Version) string {
	if cfg.GOMODCACHE == "" {
		// 不要使用当前目录。
		return ""
	}

	ziphash, err := CachePath(mod, "ziphash")
	if err != nil {
		return ""
	}
	data, err := lockedfile.Read(ziphash)
	if err != nil {
		return ""
	}
	data = bytes.TrimSpace(data)
	if !isValidSum(data) {
		return ""
	}
	return string(data)
}

// 如果数据是zip哈希文件的有效内容，则isValidSum返回true。
// 某些关键文件是通过先截断
// 然后写入实际字节写入磁盘的，因此如果写入失败
// 则损坏的文件应至少包含一个由truncate操作写入的空
// 字节。
func isValidSum(data []byte) bool {
	if bytes.IndexByte(data, '\000') >= 0 {
		return false
	}

	if len(data) != len("h1:")+base64.StdEncoding.EncodedLen(sha256.Size) {
		return false
	}

	return true
}

var ErrGoSumDirty = errors.New("updates to go.sum needed, disabled by -mod=readonly")

// WriteGoSum写下开始。sum文件，如果需要更新。
// 
// keep用于检查新添加的金额是否应保存在go中。总和
// 它应该有模块内容和go的条目。mod和
// （版本以“/go.mod”结尾）。现有金额将被保留，除非其
// 已被TrimGoSum标记为删除。
func WriteGoSum(keep map[module.Version]bool, readonly bool) error {
	goSum.mu.Lock()
	defer goSum.mu.Unlock()

	// 如果我们还没读过围棋。sum文件，不用麻烦写了。
	if !goSum.enabled {
		return nil
	}

	// 检查我们是否需要添加keep[m]为真的金额，或删除
	// 标记为TrimGoSum的未使用金额。如果没有更改，只需返回而不打开go即可。总和
	dirty := false
Outer:
	for m, hs := range goSum.m {
		for _, h := range hs {
			st := goSum.status[modSum{m, h}]
			if st.dirty && (!st.used || keep[m]) {
				dirty = true
				break Outer
			}
		}
	}
	if !dirty {
		return nil
	}
	if readonly {
		return ErrGoSumDirty
	}
	if _, ok := fsys.OverlayPath(GoSumFile); ok {
		base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay")
	}

	// 尽最大努力获取侧锁，但不允许同时编辑之前版本的'go'命令。
	if unlock, err := SideLock(); err == nil {
		defer unlock()
	}

	err := lockedfile.Transform(GoSumFile, func(data []byte) ([]byte, error) {
		if !goSum.overwrite {
			// 同时合并其他流程添加的任何金额。
			// 只添加我们实际检查的总和：用户可能已经编辑或
			// 截断了文件以删除错误的哈希，我们不应该在没有充分理由的情况下恢复
			// 它们。
			goSum.m = make(map[module.Version][]string, len(goSum.m))
			readGoSum(goSum.m, GoSumFile, data)
			for ms, st := range goSum.status {
				if st.used && !sumInWorkspaceModulesLocked(ms.mod) {
					addModSumLocked(ms.mod, ms.sum)
				}
			}
		}

		var mods []module.Version
		for m := range goSum.m {
			mods = append(mods, m)
		}
		module.Sort(mods)

		var buf bytes.Buffer
		for _, m := range mods {
			list := goSum.m[m]
			sort.Strings(list)
			for _, h := range list {
				st := goSum.status[modSum{m, h}]
				if (!st.dirty || (st.used && keep[m])) && !sumInWorkspaceModulesLocked(m) {
					fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
				}
			}
		}
		return buf.Bytes(), nil
	})

	if err != nil {
		return fmt.Errorf("updating go.sum: %w", err)
	}

	goSum.status = make(map[modSum]modSumStatus)
	goSum.overwrite = false
	return nil
}

func sumInWorkspaceModulesLocked(m module.Version) bool {
	for _, goSums := range goSum.w {
		if _, ok := goSums[m]; ok {
			return true
		}
	}
	return false
}

// TrimGoSum trims go。总和仅包含可复制
// 构建所需的模块。
// 
// keep用于检查一笔金额是否应保留在go中。摩登派青年它应该
// 包含模块内容和go的条目。mod sums（版本以“/go.mod”结尾）。
func TrimGoSum(keep map[module.Version]bool) {
	goSum.mu.Lock()
	defer goSum.mu.Unlock()
	inited, err := initGoSum()
	if err != nil {
		base.Fatalf("%s", err)
	}
	if !inited {
		return
	}

	for m, hs := range goSum.m {
		if !keep[m] {
			for _, h := range hs {
				goSum.status[modSum{m, h}] = modSumStatus{used: false, dirty: true}
			}
			goSum.overwrite = true
		}
	}
}

const goSumMismatch = `

SECURITY ERROR
This download does NOT match an earlier download recorded in go.sum.
The bits may have been replaced on the origin server, or an attacker may
have intercepted the download attempt.

For more information, see 'go help module-auth'.
`

const sumdbMismatch = `

SECURITY ERROR
This download does NOT match the one reported by the checksum server.
The bits may have been replaced on the origin server, or an attacker may
have intercepted the download attempt.

For more information, see 'go help module-auth'.
`

const hashVersionMismatch = `

SECURITY WARNING
This download is listed in go.sum, but using an unknown hash algorithm.
The download cannot be verified.

For more information, see 'go help module-auth'.

`

var HelpModuleAuth = &base.Command{
	UsageLine: "module-auth",
	Short:     "module authentication using go.sum",
	Long: `
When the go command downloads a module zip file or go.mod file into the
module cache, it computes a cryptographic hash and compares it with a known
value to verify the file hasn't changed since it was first downloaded. Known
hashes are stored in a file in the module root directory named go.sum. Hashes
may also be downloaded from the checksum database depending on the values of
GOSUMDB, GOPRIVATE, and GONOSUMDB.

For details, see https:// golang。org/ref/mod#验证。
`,
}

var HelpPrivate = &base.Command{
	UsageLine: "private",
	Short:     "configuration for downloading non-public code",
	Long: `
The go command defaults to downloading modules from the public Go module
mirror at proxy.golang.org. It also defaults to validating downloaded modules,
regardless of source, against the public Go checksum database at sum.golang.org.
These defaults work well for publicly available source code.

The GOPRIVATE environment variable controls which modules the go command
considers to be private (not available publicly) and should therefore not use
the proxy or checksum database. The variable is a comma-separated list of
glob patterns (in the syntax of Go's path.Match) of module path prefixes.
For example,

	GOPRIVATE=*.corp.example.com,rsc.io/private

causes the go command to treat as private any module with a path prefix
matching either pattern, including git.corp.example.com/xyzzy, rsc.io/private,
and rsc.io/private/quux.

For fine-grained control over module download and validation, the GONOPROXY
and GONOSUMDB environment variables accept the same kind of glob list
and override GOPRIVATE for the specific decision of whether to use the proxy
and checksum database, respectively.

For example, if a company ran a module proxy serving private modules,
users would configure go using:

	GOPRIVATE=*.corp.example.com
	GOPROXY=proxy.example.com
	GONOPROXY=none

The GOPRIVATE variable is also used to define the "public" and "private"
patterns for the GOVCS variable; see 'go help vcs'. For that usage,
GOPRIVATE applies even in GOPATH mode. In that case, it matches import paths
instead of module paths.

The 'go env -w' command (see 'go help env') can be used to set these variables
for future go command invocations.

For more details, see https:// golang。org/ref/mod#私有模块。
`,
}
